package sk.bielyvlk.vlkgps;

import android.location.Location;
import android.util.Log;

/** http://en.wikipedia.org/wiki/Orthographic_projection_(cartography) */

public class Projection {
	public static final String TAG = "vlkGPS.Projection";

	private Coordinates center;
	private double bearing;
	private float zoom;
	private double multiply;
	private double cosCenterLatitude, sinCenterLatitude;
	private boolean centerUpdated = true;
	private float x, y;
	private boolean visible;

	public Projection() {
		this.center = new Coordinates();
		zoom = 1;
		multiply = Coordinates.REARTH / zoom; 
	}

	/** set center coordinates
	 * @param center center coordinates
	 */
	public void setCenter(Coordinates center) {
		this.center.set(center);
		centerUpdated = true;
	}

	/** set center coordinates
	 * @param center center coordinates
	 */
	public void setCenter(Location center) {
		this.center.set(center);
		centerUpdated = true;
	}

	/** get center coordinates
	 * @return center coordinates
	 */
	public Coordinates getCenter() {
		centerUpdated = true;
		return center;
	}

	public void setBearing(double bearing) {
		this.bearing = bearing;
	}

	public double getBearing() {
		return bearing;
	}

	/** zoom
	 * @param zoom factor
	 */
	public void zoom(float zoom) {
		this.zoom *= zoom;
		if (this.zoom < 0.1f) this.zoom = 0.1f;
		if (this.zoom > 100000f) this.zoom = 100000f;
		Log.d(TAG, "zoom: " + this.zoom);
		multiply = Coordinates.REARTH / this.zoom; 
	}

	/** set zoom level
	 * @param zoom meters per pixel
	 */
	public void setZoom(float zoom) {
		this.zoom = zoom;
		multiply = Coordinates.REARTH / this.zoom; 
	}

	/** get zoom level
	 * @return meters per pixel
	 */
	public double getZoom() {
		return zoom;
	}

	/** calculate new x and y from coordinates
	 * @param coords coordinates
	 */
	public void calcCoordinates(Coordinates coords) {
		if (centerUpdated) {
			centerUpdated = false;
			double centerLatitude = Math.toRadians(center.latitude);
			cosCenterLatitude = Math.cos(centerLatitude);
			sinCenterLatitude = Math.sin(centerLatitude);
		}
		double centerLongitude = Math.toRadians(center.longitude);
		double coordsLatitude = Math.toRadians(coords.latitude);
		double coordsLongitude = Math.toRadians(coords.longitude);
		double cosLatitude = Math.cos(coordsLatitude);
		double sinLatitude = Math.sin(coordsLatitude);
		double c1 = cosLatitude * Math.cos(coordsLongitude - centerLongitude);
		x = (float)(multiply * cosLatitude * Math.sin(coordsLongitude - centerLongitude));
		y = (float)(multiply * (cosCenterLatitude * sinLatitude - sinCenterLatitude * c1));
		visible = sinCenterLatitude * sinLatitude + cosCenterLatitude * c1 > 0;
	}

	/** calculate new x and y from coordinates
	 * @param coords coordinates
	 */
	public void calcCoordinates(Location coords) {
		calcCoordinates(new Coordinates(coords));
	}
	
	/** is calculated coordinate visible
	 * @return true if coordinate is visible
	 */
	public boolean isVisible() {
		return visible;
	}

	/** get calculated X
	 * @return x coordinate
	 */
	public float getX() {
		return x;
	}

	/** get calculated Y
	 * @return y coordinate
	 */
	public float getY() {
		return y;
	}

	/** calculate coordinates from x and y position
	 * @param x
	 * @param y
	 * @return calculated coordinate
	 */
	public Coordinates getCoordinates(float x, float y) {
		x /= multiply;
		y /= multiply;
		double cosc = Math.sqrt(1 - (x * x + y * y));
		double sinCenterLatitude = Math.sin(Math.toRadians(center.latitude));
		double cosCenterLatitude = Math.cos(Math.toRadians(center.latitude));
		double lat = Math.asin(cosc * sinCenterLatitude + y * cosCenterLatitude);
		double lon = Math.toRadians(center.longitude) + Math.atan2(x, cosCenterLatitude * cosc - y * sinCenterLatitude);
		return new Coordinates(Math.toDegrees(lon), Math.toDegrees(lat));
	}

}
